<?php

/**
 * This file is part of the Zephir.
 *
 * (c) Phalcon Team <team@zephir-lang.com>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Zephir\Statements;

use ReflectionException;
use Zephir\CompilationContext;
use Zephir\Exception;
use Zephir\Exception\CompilerException;
use Zephir\Expression;

use function in_array;

/**
 * unset() statement
 */
class UnsetStatement extends StatementAbstract
{
    /**
     * @param CompilationContext $compilationContext
     *
     * @throws Exception
     * @throws ReflectionException
     */
    public function compile(CompilationContext $compilationContext): void
    {
        $flags = 'PH_SEPARATE';

        $compilationContext->headersManager->add('kernel/array');
        $expression = $this->statement['expr'];

        if ('list' === $expression['type']) {
            $expression = $expression['left'];
        }

        switch ($expression['type']) {
            case 'array-access':
                /**
                 * Unset of object property
                 */
                if (isset($expression['left']['type']) && $expression['left']['type'] === 'property-access') {
                    $compilationContext = $this->generateUnsetPropertyFromObject($expression, $compilationContext);
                }

                $expr = new Expression($expression['left']);
                $expr->setReadOnly(true);
                $exprVar = $expr->compile($compilationContext);
                $variable = $compilationContext->symbolTable->getVariableForWrite($exprVar->getCode(), $compilationContext, $this->statement);

                $expr = new Expression($expression['right']);
                $expr->setReadOnly(true);
                $exprIndex = $expr->compile($compilationContext);
                break;

            case 'property-access':
            case 'property-dinamic-access':
                $expr = new Expression($expression['left']);
                $expr->setReadOnly(true);
                $exprVar = $expr->compile($compilationContext);
                $variable = $compilationContext->symbolTable->getVariableForWrite($exprVar->getCode(), $compilationContext, $this->statement);
                $variableCode = $compilationContext->backend->getVariableCode($variable);

                $compilationContext->headersManager->add('kernel/object');
                $compilationContext->codePrinter->output('zephir_unset_property('.$variableCode.', "'.$expression['right']['value'].'");');

                return;

            default:
                throw new CompilerException('Cannot use expression type: '.$expression['type'].' in "unset"', $expression);
        }

        if (!in_array($variable->getType(), ['variable', 'array'])) {
            throw new CompilerException('Cannot use variable type: '.$variable->gettype().' in "unset"', $expression['left']);
        }

        if ($variable->hasDifferentDynamicType(['undefined', 'array', 'object', 'null'])) {
            $compilationContext->logger->warning(
                'Possible attempt to use non array/object in unset operator',
                ['non-valid-unset', $expression['left']]
            );
        }

        $compilationContext->backend->arrayUnset($variable, $exprIndex, $flags, $compilationContext);
    }

    /**
     * @param array              $expression
     * @param CompilationContext $compilationContext
     *
     * @return CompilationContext
     *
     * @throws Exception
     * @throws ReflectionException
     */
    private function generateUnsetPropertyFromObject(array $expression, CompilationContext $compilationContext): CompilationContext
    {
        $expr = new Expression($expression['right']);
        $expr->setReadOnly(true);
        $exprVar = $expr->compile($compilationContext);

        switch ($exprVar->getType()) {
            case 'variable':
                $variable = $compilationContext->symbolTable->getVariableForRead($exprVar->getCode(), $compilationContext, $this->statement);
                $variableRef = $compilationContext->backend->getVariableCode($variable);
                break;

            default:
                $expr = new Expression($expression['left']);
                $expr->setReadOnly(true);
                $exprVar = $expr->compile($compilationContext);
                $variable = $compilationContext->symbolTable->getVariableForWrite($exprVar->getCode(), $compilationContext, $this->statement);

                $variableRef = $compilationContext->backend->getVariableCode($variable);

                // TODO: Add more types check when parser will support them, see ArrayAccessTest.zep
                switch ($expression['right']['type']) {
                    case 'string':
                        $compilationContext->codePrinter->output('ZVAL_STR('.$variableRef.', "'.$expression['right']['value'].'");');
                        break;

                    case 'int':
                        $compilationContext->codePrinter->output('ZVAL_LONG('.$variableRef.', '.$expression['right']['value'].');');
                        break;
                }
                break;
        }

        $expr = new Expression($expression['left']['left']);
        $expr->setReadOnly(true);
        $exprVar = $expr->compile($compilationContext);
        $variable = $compilationContext->symbolTable->getVariableForWrite($exprVar->getCode(), $compilationContext, $this->statement);
        $variableCode = $compilationContext->backend->getVariableCode($variable);

        $compilationContext->headersManager->add('kernel/object');
        $compilationContext->codePrinter->output(
            'zephir_unset_property_array('.$variableCode.', ZEND_STRL("'.$expression['left']['right']['value'].'"), '.$variableRef.');'
        );

        return $compilationContext;
    }
}
